﻿using System;
using System.Collections;
using System.Data.SQLite;
using System.Linq;
using Infrastructure.Orm.Contracts;
using Infrastructure.Orm.Extensions;
using Infrastructure.Orm.UnitOfWork.Commands.Interfaces;
using Infrastructure.Orm.UnitOfWork.Commands.Implementations;
using System.Collections.Generic;

namespace Infrastructure.Orm.UnitOfWork.Mappings
{
    internal class UpdateMappings : CommandMappingsBase
    {
        internal UpdateMappings(TablesTree mappingsTree, SQLiteConnection connection)
            : base(mappingsTree, connection)
        { }

        protected override void CreateSpecificMappings(Tree<Table> mappingsTree)
        {
            var keyed = mappingsTree.Children.Where(c => typeof(IKeyedPersistent).IsAssignableFrom(c.Node.NodeType));
            foreach (var child in keyed)
            {
                child.Node.UpdateCommand = GenericUpdateCommand(child.Node.NodeType);
                CreateSpecificMappings(child);
            }
        }

        private static IUpdateCommand GenericUpdateCommand(Type type)
        {
            var ctorParamsTypes = new Type[] { };
            var ctorParams = new object[] { };
            var genericType = typeof(UpdateCommand<>).MakeGenericType(new[] { type });
            var ctor = genericType.GetConstructor(ctorParamsTypes);
            var built = ctor.Invoke(ctorParams);
            return (IUpdateCommand)built;
        }

        public void Run(IKeyedPersistent toPersist)
        {
            var mapping = MappingsTree.Children.First(child => child.Node.NodeType.IsAssignableFrom(toPersist.GetType()));
            RunCommands(mapping, Connection, toPersist);
        }

        private static void RunCommands(Tree<Table> mappings, SQLiteConnection connection, IKeyedPersistent toPersist)
        {
            mappings.Node.UpdateCommand.Run(connection, toPersist);
            toPersist.IsModified = false;
            foreach (var child in mappings.Children)
            {
                foreach (var descendant in GetDescendants(toPersist, child))
                {
                    RunCommands(child, connection, descendant);
                }
            }
        }
        private static IEnumerable<IKeyedPersistent> GetDescendants(IPersistent parent, Tree<Table> mappingTreeItem)
        {
            var table = mappingTreeItem.Node.CreateTableCommand.TableName;
            var descendantsPropertyInfo = parent.GetType().GetProperties().First(
                propertyInfo =>
                propertyInfo.IsRelationship() && propertyInfo.GetRelationshipAttribute().TableName.Equals(table));
            var descendants = descendantsPropertyInfo.GetValue(parent, null);
            if (descendantsPropertyInfo.IsOneToMany())
            {
                return ((IEnumerable)descendants).OfType<IKeyedPersistent>().ExpungeNulls().ToArray();
            }
            if (descendantsPropertyInfo.IsOneToOne())
            {
                if (descendants == null)
                    return Enumerable.Empty<IKeyedPersistent>();
                return new[] { (IKeyedPersistent)descendants };

            }
            throw new InvalidOperationException();
        }
    }
}